我們在前幾天已經把前後台區分開來了
不過如果後台的路徑被使用者猜到,那也是很頭痛的事情
所以我們就需要做權限管理
Pundit 可以幫我們達到這個效果
Pundit 可以讓我們輕鬆達成權限管理,
我們可以透過 policy 來決定使用者所能接觸到的網頁或範圍,
而 scope 則可以幫我們依照使用者的位階或者身份來分出顯示資訊。
gem 'pundit'
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
include Pundit::Authorization
end
會自動作出 application_policy.rb 檔案,可以把它想像成是 application_controller ,為最上層的概念
Policy 代表政策的意思,不管是在現實世界還是在網路世界,我們必須都要有政策,並且規範所有人的權利
因此我們也需要在網站中明定,誰有權利存取特定功能
其中的 application_policy 就像是中華民國的憲法一樣,為最高的政策
> rails g pundit:install
create app/policies/application_policy.rb
我們先來看看 application_policy
這個檔案
一開始初始化的時候,
就會把 current_user 以 user 帶入,
而 record 則是每個 model
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
...
class Scope
...
end
end
接下來我們來看一下 Policy 裡面的每個方法
false 表示每個人都可以看到
而有幾個比較特別的是 new 方法裡面有 create? 這表示 new 的權限應該要跟 create 一樣
但為什麼不是 create 中包著 new? 方法呢?主要是因為能 create 的人就一定能到 new 的頁面,不能 create 的人就不應該進入 new 頁面
# app/policies/application_policy.rb
def index?
false
end
def new?
create?
end
我們再來看到 Scope,
當我們需要設定特定的權限或者方法時,可以把它放在 Scope 中
初始化的時候會去呼叫 current_user ,作為 user
scope 則可能是 model 或者是關聯性 model
每個 policy class 都會有 Scope
class Scope
def initialize(user, scope)
@user = user
@scope = scope
end
def resolve
raise NotImplementedError, "You must define #resolve in #{self.class}"
end
private
attr_reader :user, :scope
end
當使用者不小心進到沒有權限的網頁,會噴錯誤訊息,
但我們總不可能讓使用者看到錯誤訊息吧?應該是要讓他看到沒有權限的頁面
我們需要去拯救 Pundit 的 NotAuthorizeError ,使用 rescue_from 來接住,並且提供回應的方法
而我們希望能回給他一個 403 頁面,告訴他你沒有足夠的權限
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized
render file: "#{Rails.root}/public/403.html",
layout: false,
status: :forbidden
end
end
因為要設定的權限有點多,我們就只拿後台的 drinks index 頁面來說
後台只有 商店的員工、商店老闆以及管理員看得到
為了方便管理,我把後台的 policy 檔案整理到 admin 資料夾中,所以必須加上 module Admin
我們在 index? 方法就需要去判斷 current_user 是不是以上角色
不是的話就會 raise Pundit::NotAuthorizedError,並且依照我們上一步設定的,呈現 403 無權限的畫面給他
# app/policies/admin/drink_policy.rb
module Admin
class DrinkPolicy < ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
@user = user
@record = record
end
def index?
user.employee? || user.owner? || user.admin?
end
...
class Scope
...
end
end
end
由於我們的 policy 是 namespace ,
所以必須要用 [:admin, :drink]
他才知道要去找 Admin::DrinkPolicy
我們就會在 DrinkPolices 的 index? 方法中判斷目前的使用者角色,是否可以存取這個 record
module Admin
class DrinksController < AdminController
...
def index
authorize([:admin, :drink])
@pagy, @drinks = pagy(Drink.with_deleted)
end
...
end
end
目前權限設定就差不多了,其他的方法也跟著照做應該就行囉
我們目前還用不到 Scope
Scope 主要是用在當今天的資料需要依照身份的不同而呈現不同才需要
例如,今天總經理需要看到所有店家的營業額,一般員工只需要看到他們自己店家的營業額時,我們才會用到 Scope
如果有使用到的話,也可以參考官方手冊,裡面寫的都蠻詳細的
以上是我們今天的權限設定~